/* vccd/vcc.c 
 * 
 * This file is part of vccd. 
 * 
 * vccd is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * vccd is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with vccd. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>

#include <vcc/vcc.h>
#include <vcc/vccd.h>
#include <vcc/version.h>
#include <klist.h>


struct klist_node connections = KLIST_NODE_INIT(&connections);
extern int nusers;


int vcc_new_connection(int fd, struct in_addr *addr) {
	struct klist_node *n;

	vcc_log("New connection from %s\n", inet_ntoa(*addr));
	vcc_info("ICONN %s %d\n", inet_ntoa(*addr), fd);

	if (unlikely(!(n = amalloc(sizeof(struct klist_node))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	klist_init(n);
	n->data = addr;
	n->value = fd;
	n->stat = STAT_UNLOGGED;

	if (unlikely(!(n->sending = amalloc(sizeof(struct klist_node))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	klist_init(n->sending);
	klist_add(&connections, n);

	/* if we kill vcc before it logs in, the nusers will dec but it 
	 * didn't login the value will be something bad. */

	nusers++;

	return 0;
}

int vcc_close_connection(int fd) {
	struct klist_node *n, *t;

	if (unlikely(!(n = find_connection(fd)))) 
		return 1;

	nusers--;

	do_user_logout(fd);
	klist_del(&connections, n);

	for (t = n->sending->next; t != n->sending; t = t->next) 
		/* it's dangerous here */
		if (t->data) 
			afree(t->data, REQ_SIZE);
	

	afree(n->sending, sizeof(struct klist_node));
	vcc_log("Client closed. \n");
	vcc_info("ILEFT %d\n", fd);

	return 0;
}


struct klist_node *find_connection(int fd) {
	struct klist_node *n;

	for (n = connections.next; n != &connections
			&& n->value != fd; n = n->next) 
		;

	if (unlikely(n == &connections)) {
		vcc_log("--- connection not found: %d\n", fd);
		vcc_info("WCONNOTFND %d\n", fd);

		return NULL;
	}

	return n;
}


int write_packages(struct klist_node *node) {
	struct klist_node *n;

	if (unlikely(!node->sending)) 
		return 0;

	if (unlikely(klist_empty(node->sending))) 
		return 0;

	for (n = node->sending->next; n != node->sending; n = n->next) {
		write(node->value, n->data, REQ_SIZE);
		klist_del(node->sending, n);

		afree(n->data, REQ_SIZE);
		afree(n, sizeof(struct klist_node));
	}
	
	return 0;
}

int vcc_write_packages(int fd) {
	return write_packages(find_connection(fd));
}

int vcc_write_all(void) {
	struct klist_node *n;

	for (n = connections.next; n != &connections; n = n->next) 
		vcc_write_packages(n->value);
	
	return 0;
}


int vcc_send_req(struct klist_node *node, void *p) {
	struct klist_node *n;

	if (unlikely(!(n = amalloc(sizeof(struct klist_node))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	klist_init(n);

	n->data = p;
	klist_add(node->sending, n);

	return 0;
}


/* send to each user include who sends this message, but excluding 'except_fd' */

int vcc_message_broadcast(char *msg, char *usrname, int except_fd, int flags) {
	struct vcc_request *req;
	struct klist_node *n;
	int i;

	if (ntohl(flags) & FLAG_ENCRYPTED) 
		vcc_log("Encrypted message. \n");

	for (n = connections.next, i = 0; n != &connections; n = n->next) {
		if (n->value == except_fd) 
			continue;

		if (n->stat == STAT_UNLOGGED) 
			continue;

		if (unlikely(!(req = amalloc(sizeof(struct vcc_request))))) {
			vcc_log("*** malloc() failed\n");

			continue;
		}

		memset(req, 0, sizeof(struct vcc_request));

		req->magic = htonl(VCC_MAGIC);
		req->reqtype = htonl(REQ_MSG_NEW);
		req->uid = 0;
		req->session = 0;

		/* no htonl(), because we didn't ntohl() */
		req->flags = flags;

		strncpy(req->msg, msg, MSG_SIZE);
		strncpy(req->usrname, usrname, USRNAME_SIZE);

		vcc_send_req(n, req);
		i++;
	}

	vcc_write_all();
	vcc_log("Message sent to %d users. \n", i);

	return 0;
}


int vcc_update_usrname(int fd, const char *usrname) {
	struct klist_node *n = find_connection(fd);

	if (unlikely(!n)) 
		return -1;

	if (likely(n->usrname)) 
		return 0;

	n->usrname = strndup(usrname, USRNAME_SIZE);
	vcc_log("user name %s updated for %d\n", usrname, fd);
	vcc_info("IUSR %s %d\n", usrname, fd);

	return 0;
}


int send_users(int fd) {
	struct vcc_relay_header 	req;
	struct klist_node 		*n;
	int 				i, size;
	char 				*buf;

	size = USRNAME_SIZE * nusers;

	if (unlikely(!(buf = amalloc(size)))) {
		vcc_log("*** malloc() failed\n");
		vcc_log("*** %d users, %d bytes. ", nusers, size);

		return 1;
	}

	vcc_log("%d users online. \n", nusers);

	req.magic = htonl(VCC_MAGIC_RL);
	req.reqtype = htonl(REQ_CTL_USRS);
	req.session = 0;

	for (n = connections.next, i = 0; n != &connections; n = n->next, i++) 
		strncpy(buf + i * USRNAME_SIZE, n->usrname ? n->usrname : "unnamed", USRNAME_SIZE);

	req.uid = htonl(i);
	relay_send_packet(fd, buf, &req, size);

	vcc_log("%d user names sent to %d\n", i, fd);
	afree(buf, size);

	return 0;
}


int vcc_new_msg(int fd, struct vcc_request *req) {
	struct klist_node 	*n = find_connection(fd);
	char 			*msg;
	int 			sid, magic;

	if (unlikely(!n)) 
		return 1;

	msg = req->msg;

	/* it's not so beautiful... but arguments of each function are not common. 
	 *
	 * so we CAN'T use requests[htonl(req->reqtype)] cetera instead. 
	 * f**k. 
	 * */

	magic = ntohl(req->magic);

	if (magic != VCC_MAGIC && magic != VCC_MAGIC_RL) {
		vcc_log("--- bad magic from %d. \n", fd);
		vcc_info("WBADMAGIC %d\n", fd);

		return 1;
	}

	switch (htonl(req->reqtype)) {
	case REQ_MSG_NEW:
		/* POLLOUT will be set, just because they sent us a message */

		return 0;

	case REQ_MSG_SEND:
		check_logged(fd);
		if (((unsigned char *) msg)[0] == 0xffu || !*msg) 
			return 0;

		sid = ntohl(req->session);

		vcc_log("(%d) New message\n", sid);

		if (!sid) 
			vcc_message_broadcast(msg, req->usrname, fd, req->flags);

		else 
			session_broadcast(sid, msg, req->usrname, fd, req->flags);

		break;

	case REQ_CTL_USRS:
		check_logged(fd);
		send_users(fd);

		break;

	case REQ_CTL_LOGIN:
		do_user_login(fd, req);
		vcc_update_usrname(fd, req->usrname);

		break;

	case REQ_CTL_IALOG:
		check_logged(fd);
		do_ia_login(fd, req);
		vcc_update_usrname(fd, req->usrname);

		break;

	case REQ_CTL_NEWSE:
		check_logged(fd);
		create_session(fd, req->usrname);

		break;

	case REQ_CTL_SESS:
		check_logged(fd);
		send_sessions(fd);

		break;

	case REQ_CTL_JOINS:
		check_logged(fd);
		join_session(ntohl(req->session), fd);

		break;

	case REQ_CTL_UINFO:
		check_logged(fd);
		get_user_info(fd, req);

		break;

	case REQ_SYS_SCRINC:
		check_logged(fd);
		incr_score(fd, req);

		break;

	case REQ_SYS_INFO:
		check_logged(fd);
		monitor_info(fd, req);

		break;

	case REQ_CTL_SENAME:
		check_logged(fd);
		session_name(fd, req);

		break;

	default:
		vcc_log("--- bad request type from %d\n", fd);
		vcc_info("WBADREQ %d\n", fd);
	
		break;
	}

	vcc_write_packages(fd);

	return 0;
}


